# include <iostream>

using namespace std;

typedef long prime_number; // use sfixn in Fourier Prime  case, ideally, should be int_32 
typedef long Integer; // int_32 related to the prime_number choice   

/* The type of of ZpNumber is a parametric type determined by a prime
 */
template <const prime_number p=2> struct ZpNumber{ 
	private:
	Integer _val;
	public:
	ZpNumber(const Integer& i=0) : _val(i){
		_val=i % p;
	}
	/* +, -, *, %, add, mul, power, is_invertible, invert, is_zero */
	Integer get_value() {return _val;}


	/* operator + */
	ZpNumber operator + (const ZpNumber& op2)
	{
		return (_val+op2._val); // will call the construtor
	}

	/* operator == */
	bool operator==(const ZpNumber<p>& op){return _val=op._val;}


	friend ostream& operator <<(ostream& output, const ZpNumber& i)
	{
		output << i._val;
	}

	/* another addition functor, call by name*/
	friend const ZpNumber& add(ZpNumber& output, const ZpNumber& op1, const ZpNumber& op2)
	{
		output._val=(op1._val+op2._val) % p;
	}
};

/* The type of of ZpRing is a parametric type determined by a prime also,
 * and it has delares its own typename Element
 * when never user use the Element to do computation, the is no mix-ring problem
 * mix ring error will be detect at run time
 */


template <const prime_number p=2> struct ZpRing{
	private:
		const prime_number _characteristic;
	public:
		typedef ZpNumber<p> Element;
		ZpRing() : _characteristic(p) {}
		Element get_zero() {return Element(0);}
};

int main(){
	ZpRing<2> Z_2;
	ZpRing<3> Z_3;
	ZpRing<2>::Element p(5), q(1), p_q; // think about the type vector<int>::iterator
	ZpRing<3>::Element r(5);
	cout << "p: " << p << "\t" << "q: " << q << "\t" << "r: " << r << endl;
	
	cout << "p+q (by add): " << add(p_q,p,q) << endl; // now p_q holds p+q, for expert or developper use,
	cout << "p+q (by operator): " << p+q << endl;

	cout << "Z_2 zero: " << Z_2.get_zero() << "\t" << "Z_3 zero: " << Z_3.get_zero() << endl;

	// some mixed ring type errors
	
	/*	cout << (p==q) << endl;*/ // compiling error because of mixed type 
	/* cout << add(p,p,r) << endl;*/ // compiling error because of mixed type
	/* cout << p+r << endl;*/ // compiling error because of mixed type
	return 0;
}
